﻿using JustMetClient.Socket.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace JustMetClient.Socket
{
    public delegate void ReadHandle(object sender, MessageModel e);
    public delegate void StatusHandle(object sender, SocketStatusArgs e);
    public class SocketTool
    {
        #region 变量
        private object WriteLocker = new object();//写入流锁定
        private object ConnectLocker = new object();//连接锁定
        private TcpClient TcpClient;//客户端连接
        private int ConnectErrorTimes = 0;//连接出错次数
        private string IP = "";//IP地址
        private int Port = 0;//端口号
        private int TimeOut = 10 * 1000;//等待接收数据时间

        public event ReadHandle ReadEvent;
        public event StatusHandle StatusEvent;
        #endregion

        #region 构造方法
        /// <summary>
        /// 私有化默认无参数构造方法
        /// </summary>
        private SocketTool() { }
        /// <summary>
        /// 创建Socket通信
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        public SocketTool(string ip, int port)
        {
            IP = ip;
            Port = port;
            TcpClient = new TcpClient();
            TcpClient.ReceiveTimeout = TimeOut;
        }
        #endregion

        #region 连接、断开Socket
        /// <summary>
        /// 连接Socket通信
        /// </summary>
        /// <returns></returns>
        public bool Connect()
        {
            lock (ConnectLocker)
            {
                if (TcpClient == null) return false;

                try
                {
                    if (!TcpClient.Connected)
                    {
                        for (int i = 0; i < 3; i++)
                        {
                            TcpClient.Connect(IPAddress.Parse(IP), Port);
                            if (TcpClient.Connected)
                            {
                                ConnectErrorTimes = 0;
                                UpdateStatus(SocketStatus.已开始);
                                break;
                            }
                            if (i == 2)
                            {
                                UpdateStatus(SocketStatus.异常);
                            }
                            Thread.Sleep(500);
                        }
                    }

                }
                catch
                {
                    ConnectErrorTimes++;
                    TcpClient.Close();

                    Thread.Sleep(1000);
                    if (ConnectErrorTimes > 3) Thread.Sleep(10 * 1000);

                    TcpClient = new TcpClient();
                    UpdateStatus(SocketStatus.异常);
                }
            }

            bool isConnected = false;
            try
            {
                isConnected = TcpClient == null ? false : TcpClient.Connected;
                if (isConnected) UpdateStatus(SocketStatus.已连接);
                else UpdateStatus(SocketStatus.异常);
            }
            catch { }
            return isConnected;
        }
        /// <summary>
        /// 断开Socket通信
        /// </summary>
        /// <returns></returns>
        public bool Close()
        {
            try
            {
                if (TcpClient.Connected)
                {
                    TcpClient.Close();
                    UpdateStatus(SocketStatus.未连接);
                }
            }
            catch { return false; }
            return true;
        }
        #endregion

        #region 读写方法
        /// <summary>
        /// 启动Socket通信
        /// </summary>
        public void Read()
        {
            if (Connect())
            {
                string message = "";
                NetworkStream nsm = TcpClient.GetStream();
                List<byte> bytes = new List<byte>();
                try
                {
                    int bufferSize = 1024;
                    byte[] buffer = new byte[bufferSize];
                    int count = nsm.Read(buffer, 0, bufferSize);
                    bytes.AddRange(buffer.Take(count));
                    message = Encoding.GetEncoding("GBK").GetString(bytes.ToArray());
                    List<string> msgs = MessageFormatter.SplitMessage(message);
                    //循环处理消息
                    foreach (var msg in msgs)
                    {
                        //处理消息
                        MessageModel model = MessageFormatter.String2Model(msg);
                        ReadEvent?.Invoke(this, model);
                    }
                }
                catch (Exception ex)
                {
                }
            }
        }
        public bool SendMessage(MessageModel model)
        {
            string message = MessageFormatter.Model2String(model);
            return Write(message);
        }
        public bool SendHeartbeat()
        {
            return Write("K\n");
        }
        private bool Write(string s)
        {
            if (Connect())
            {
                lock (WriteLocker)
                {
                    try
                    {
                        NetworkStream ntwStream = TcpClient.GetStream();
                        if (ntwStream.CanWrite)
                        {
                            byte[] bytSend = Encoding.GetEncoding("GBK").GetBytes(s);
                            ntwStream.Write(bytSend, 0, bytSend.Length);
                            return true;
                        }
                        else
                        {
                            //无法写入数据流
                            ntwStream.Close();
                            TcpClient.Close();
                        }
                    }
                    catch { }
                }
            }
            return false;
        }
        #endregion

        #region 状态监控
        /// <summary>
        /// 更新通信状态
        /// </summary>
        /// <param name="status"></param>
        private void UpdateStatus(SocketStatus status)
        {
            string localIP = "";
            try
            {
                IPEndPoint ip = (IPEndPoint)TcpClient.Client.LocalEndPoint;
                localIP = ip?.Address.ToString();
            }
            catch { }
            StatusEvent?.Invoke(this, new SocketStatusArgs(status, localIP));
        }
        #endregion
    }
}
